home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/perl
- ###############
-
- ##
- # Name: msfpescan
- # Author: H D Moore <hdm [at] metasploit.com>
- # Version: $Revision: 1.34 $
- # Description: Search PE files for given opcodes
- # License:
- #
- # This file is part of the Metasploit Exploit Framework
- # and is subject to the same licenses and copyrights as
- # the rest of this package.
- #
- ##
-
- require 5.6.0;
-
- use FindBin qw{$RealBin};
- use lib "$RealBin/lib";
- use Getopt::Std;
- use strict;
-
- use Pex::PEInfo;
- use Pex;
- use Pex::Nasm::Ndisasm;
-
- use Msf::ColPrint;
- use Msf::TextUI;
-
- no utf8;
- no locale;
-
- Msf::UI::ActiveStateSucks();
- Msf::UI::BrokenUTF8();
-
- my $VERSION = '$Revision: 1.34 $';
-
- my %opts = ();
- my %jmps =
- (
- "\xff\xd0" => ["eax", "call"],
- "\xff\xe0" => ["eax", "jmp" ],
- "\xff\xd1" => ["ecx", "call"],
- "\xff\xe1" => ["ecx", "jmp" ],
- "\xff\xd2" => ["edx", "call"],
- "\xff\xe2" => ["edx", "jmp" ],
- "\xff\xd3" => ["ebx", "call"],
- "\xff\xe3" => ["ebx", "jmp" ],
- "\xff\xe4" => ["esp", "jmp" ],
- "\xff\xd5" => ["ebp", "call"],
- "\xff\xe5" => ["ebp", "jmp" ],
- "\xff\xd6" => ["esi", "call"],
- "\xff\xe6" => ["esi", "jmp" ],
- "\xff\xd7" => ["edi", "call"],
- "\xff\xe7" => ["edi", "jmp" ],
-
- "\x50\xc3" => ["eax", "push"],
- "\x53\xc3" => ["ebx", "push"],
- "\x51\xc3" => ["ecx", "push"],
- "\x52\xc3" => ["edx", "push"],
- "\x54\xc3" => ["esp", "push"],
- "\x55\xc3" => ["ebp", "push"],
- "\x56\xc3" => ["esi", "push"],
- "\x57\xc3" => ["edi", "push"],
- );
-
- my %pops =
- (
- "eax" => "\x58",
- "ebx" => "\x5b",
- "ecx" => "\x59",
- "edx" => "\x5a",
- "esi" => "\x5e",
- "edi" => "\x5f",
- "ebp" => "\x5d",
- );
-
- getopts("f:d:j:sx:a:B:A:I:nhvDES", \%opts);
- Usage() if($opts{'h'});
- Version() if($opts{'v'});
-
- if ($opts{'h'} ||
- (! defined($opts{'f'}) && ! defined($opts{'d'})) ||
- (! defined($opts{'j'}) &&
- ! defined($opts{'x'}) &&
- ! defined($opts{'a'}) &&
- ! defined($opts{'D'}) &&
- ! defined($opts{'S'}) &&
- ! $opts{'s'})
- )
- {
- Usage();
- exit(0);
- }
-
- my $func;
- my $args = { };
-
- if(exists($opts{'s'})) {
- $func = \&popPopRet;
- }
- elsif(exists($opts{'j'})) {
- $func = \&jmpReg;
- $args->{'reg'} = $opts{'j'};
- }
- elsif(exists($opts{'x'})) {
- $func = \®ex;
- $args->{'regex'} = $opts{'x'};
- }
- elsif(exists($opts{'a'})) {
- $func = \&address;
- $args->{'address'} = hex($opts{'a'});
- }
- elsif(exists($opts{'D'})) {
- $func = \&dumpinfo;
- $args->{'dumpinfo'} = hex($opts{'D'});
- }
- elsif(exists($opts{'S'})) {
- $func = \&identify;
- $args->{'identify'} = hex($opts{'S'});
- }
-
- $args->{'before'} = $opts{'B'} if(exists($opts{'B'}));
- $args->{'after'} = $opts{'A'} if(exists($opts{'A'}));
-
- if($opts{'f'}) {
-
- my $filename = $opts{'f'};
- my $pe = Pex::PEInfo->new('File' => $filename, 'Debug' => $opts{'E'}, 'FullResources' => 1);
-
- if (! $pe)
- {
- print STDERR "$0: could not load PE image from file.\n";
- exit(0);
- }
-
- if ($opts{'I'}) { $pe->ImageBase($opts{'I'}) }
-
- &{$func}($pe, $args);
- }
- else {
- my $dir = $opts{'d'};
- opendir(INDIR, $dir);
- my @files = readdir(INDIR);
- closedir(INDIR);
- foreach my $file (@files) {
- if($file =~ /^(.{8})\.rng/) {
-
- #print "Good file: $dir $file\n";
- my $pe = SkapeFoo->new($dir . '/' . $file, hex($1));
- &{$func}($pe, $args);
- }
- }
- }
-
- # Identify the binary based on signatures
- sub identify {
- my $pe = shift;
- my $data = $pe->Raw;
- my $args = shift;
- my $sigf = $RealBin.'/data/msfpescan/identify.txt';
- my %sigs = ();
-
- my $ep = $pe->VirtualToOffset($pe->EntryPoint + $pe->ImageBase);
- my $filename = $pe->Filename;
-
-
- if(! open(SIGS, "<" .$sigf)) {
- print STDERR "[*] Could not load the signature database: $!\n";
- return;
- }
-
- # If ep_only is set, only scan from the endpoint offset
- my $edata = substr($data, $ep, 8192);
-
- my $name;
- my $regx;
- my $ep_only = 0;
- my $ep_off = 0;
- my $sidx = 0;
-
- # If the entrypoint was mangled, disable ep scanning
- if (! length($edata)) {
- $ep_off = 1;
- }
-
- while (my $line = <SIGS>) {
-
- next if $line =~ /^\s*#/;
-
- if ($line =~ m/\[(.*)\]/) {
- if ($name) {
- $sigs{$name} = [$regx, $ep_only];
- }
- $name = $1." [".$sidx++."]";
- $ep_only = 0;
- next;
- }
-
- if ($line =~ m/signature\s*=\s*(.*)\s*/i) {
- my $pattern = $1;
- $regx = '';
- foreach my $c (split(/\s+/, $pattern)) {
- if ($c eq '??') {
- $regx .= '.';
- }else{
- $regx .= "\\x".$c;
- }
- }
- }
- if ($line =~ m/ep_only\s*=\s*T.*\s*/i) {
- $ep_only = 1 if ! $ep_off;
- }
- }
- if ($name) {
- $sigs{$name} = [$regx, $ep_only];
- }
-
- foreach my $sig (keys %sigs) {
- my $regex = $sigs{$sig}->[0];
- my @addrs= ();
-
- # Only compare the entry point?
- if ($sigs{$sig}->[1]) {
-
- if ($edata =~ /^($regex)/) {
- push @addrs, $pe->EntryPoint + $pe->ImageBase;
- }
-
- } else {
- while($data =~ m/($regex)/g) {
- my $found = $1;
- my $index = pos($data) - length($found);
- my $va = $pe->OffsetToVirtual($index);
- push @addrs, $va if $va;
- }
- }
- if (@addrs) {
- print "$filename: $sig (".scalar(@addrs)." matches)\n";
- }
- }
- }
-
- # Scan for pop/pop/ret addresses
- sub popPopRet
- {
- my $pe = shift;
- my $data = $pe->Raw;
- my $args = shift;
- foreach my $rA (keys(%pops))
- {
- foreach my $rB (keys(%pops))
- {
- my $opc = $pops{$rA} . $pops{$rB} . "\xc3";
- my $lst = 0;
- my $idx = index($data, $opc, $lst);
- while ($idx > 0)
- {
- my $va = $pe->OffsetToVirtual($idx);
- printf("0x%.8x $rA $rB ret\n", $va) if $va;
- $lst = $idx + 1;
- $idx = index($data, $opc, $lst);
- }
- }
- }
- }
-
- # Scan for jmp/call/push,ret addresses
- sub jmpReg
- {
- my $pe = shift;
- my $data = $pe->Raw;
- my $args = shift;
- my $reg = $args->{'reg'};
- foreach my $opc (keys(%jmps))
- {
- next if ($reg && lc($reg) ne $jmps{$opc}->[0]);
-
- my $lst = 0;
- my $idx = index($data, $opc, $lst);
- while ($idx > 0)
- {
- my ($reg, $typ) = @{$jmps{$opc}};
-
- my $va = $pe->OffsetToVirtual($idx);
- printf("0x%.8x $typ $reg\n", $va) if $va;
- $lst = $idx + 1;
- $idx = index($data, $opc, $lst);
- }
- }
- }
-
- # Regex
- sub regex {
- my $pe = shift;
- my $data = $pe->Raw;
- my $args = shift;
- my $regex = $args->{'regex'};
- $regex .= '.' x $args->{'after'} if($args->{'after'});
- $regex = ('.' x $args->{'before'}) . $regex if($args->{'before'});
-
- while($data =~ m/($regex)/g) {
- my $found = $1;
- my $index = pos($data) - length($found);
- my $va = $pe->OffsetToVirtual($index);
- printf("0x%.8x %s\n", $va, hexOutput($found)) if $va;
- }
- }
-
- sub address {
- my $pe = shift;
- my $data = $pe->Raw;
- my $args = shift;
-
- my $address = $args->{'address'} - $args->{'before'};
- my $length = $args->{'before'} + $args->{'after'};
- $length = 1 if(!$length);
- my $index = $pe->VirtualToOffset($address);
- my $found = substr($data, $index, $length);
- return if(!defined($index) || length($found) == 0);
- printf("0x%.8x %s\n", $address, hexOutput($found));
- }
-
- sub dumpinfo {
- my $pe = shift;
- my $args = shift;
-
- my @img_hdrs = $pe->ImageHeaders;
- my @opt_img_hdrs = $pe->OptImageHeaders;
- my $imports = $pe->Imports;
- my $exports = $pe->Exports;
- my $resources = $pe->Resources;
- my $version = $pe->VersionStrings;
- my $col;
- my %setUEF = ();
- my $setUEF_IAT = 0;
-
- print "\n\n[ Image Headers ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
- foreach my $hdr (@img_hdrs) {
- $col->AddRow($hdr, sprintf("0x%.8x",$pe->ImageHeader($hdr)));
- }
- print $col->GetOutput;
-
- print "\n\n[ Optional Headers ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
- foreach my $hdr (@opt_img_hdrs) {
- $col->AddRow($hdr, sprintf("0x%.8x",$pe->OptImageHeader($hdr)));
- }
- print $col->GetOutput;
-
- print "\n\n[ Exported Functions ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
- foreach my $name (@{ $exports->{'ordinals'} }) {
- my $add = $exports->{'funcs'}->{$name}->{'add'};
- my $ord = $exports->{'funcs'}->{$name}->{'ord'};
- next if ! $ord;
- $col->AddRow($ord, $name, sprintf("0x%.8x",$add));
- }
- print $col->GetOutput;
-
- print "\n\n[ Imported Functions ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
- foreach my $module (keys(%{ $imports })) {
- foreach my $func (sort(keys(%{ $imports->{$module} }))) {
- $col->AddRow($module, $func,
- "IAT ".sprintf("0x%.8x", $imports->{$module}->{$func}->{'iat'})
- );
-
- if (lc($func) eq 'setunhandledexceptionfilter') {
- $setUEF_IAT = $imports->{$module}->{$func}->{'iat'};
- }
- }
- $col->AddRow("", "", "");
- }
- print $col->GetOutput;
-
- print "\n[ Resources ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
- foreach my $type (sort(keys(%{ $resources->{'Types'} }))) {
- foreach my $name (sort(keys(%{ $resources->{'Types'}->{$type} }))) {
- my $entry = $resources->{'Entries'}->{$name};
- $col->AddRow($name, $entry->{'Name'}, "CP ".$entry->{'Code'}, $entry->{'Size'}." bytes");
- }
- }
- print $col->GetOutput;
-
- print "\n\n[ Version Strings ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
- foreach my $lang (keys(%{ $version })) {
- foreach my $name (sort(keys(%{ $version->{$lang} }))) {
- $col->AddRow($lang, $name, $version->{$lang}->{$name});
- }
- }
- print $col->GetOutput;
-
-
- return if not $setUEF_IAT;
-
-
- print "\n\n[ SetUnhandledExceptionFilter ]\n\n";
- $col = Msf::ColPrint->new(4, 4);
-
- my $data = $pe->Raw;
- my $regex = "(\x68|\xff\x15)".pack("V", $setUEF_IAT);
- $regex .= '.' x $args->{'after'} if($args->{'after'});
- $regex = ('.' x $args->{'before'}) . $regex if($args->{'before'});
-
- while($data =~ m/($regex)/g) {
- my $found = $1;
- my $index = pos($data) - length($found);
- my $va = $pe->OffsetToVirtual($index);
- if ($va) {
- $col->AddRow(sprintf("0x%.8x", $va), hexOutput($found));
- }
- }
- print $col->GetOutput;
- }
-
- sub hexOutput {
- my $data = shift;
- my $string = unpack('H*', $data);
- if($opts{'n'}) {
-
- # my $tempString = $string;
- # $tempString =~ s/(..)/\\x$1/g;
- $string .= "\n--- ndisasm output ---\n";
-
- # $string .= `echo -ne "$tempString" | ndisasm -u /dev/stdin`;
- $string .= Pex::Nasm::Ndisasm->DisasData($data);
- $string .= "--- ndisasm output ---";
- }
- return($string);
- }
-
- sub Usage
- {
- print STDERR
- qq{ Usage: $0 <input> <mode> <options>
- Inputs:
- -f <file> Read in PE file
- -d <dir> Process memdump output
- Modes:
- -j <reg> Search for jump equivalent instructions
- -s Search for pop+pop+ret combinations
- -x <regex> Search for regex match
- -a <address> Show code at specified virtual address
- -D Display detailed PE information
- -S Attempt to identify the packer/compiler
- Options:
- -A <count> Number of bytes to show after match
- -B <count> Number of bytes to show before match
- -I address Specify an alternate ImageBase
- -n Print disassembly of matched data
-
- };
- exit(0);
-
- }
- sub Version {
- my $ver = Pex::Utils::Rev2Ver($VERSION);
- print STDERR qq{
- Msfpescan Version: $ver
-
- };
- exit(0);
- }
-
- package SkapeFoo;
- use strict;
-
- sub new {
- my $class = shift;
- my $self = bless({ }, $class);
- $self->Filename(shift);
- $self->Base(shift);
- $self->ReadInRaw;
- return($self);
- }
- sub Filename {
- my $self = shift;
- $self->{'Filename'} = shift if(@_);
- return($self->{'Filename'});
- }
- sub Base {
- my $self = shift;
- $self->{'Base'} = shift if(@_);
- return($self->{'Base'});
- }
- sub Raw {
- my $self = shift;
- $self->{'Raw'} = shift if(@_);
- return($self->{'Raw'});
- }
-
- sub ReadInRaw {
- my $self = shift;
- open(INFILE, '<' . $self->Filename) or return(0);
- local $/;
- my $data = <INFILE>;
- close(INFILE);
- $self->Raw($data);
- return(1);
- }
-
- sub VirtualToOffset {
- my $self = shift;
- my $virtual = shift;
- return($virtual - $self->Base);
- }
-
- sub OffsetToVirtual {
- my $self = shift;
- my $offset = shift;
- return($offset + $self->Base);
- }
-
- 1;
-